package com.taotaojs.util.reflect;

import com.taotaojs.exception.MyInnerException;
import com.taotaojs.util.CollectionUtil;

import java.io.*;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 实体类拷贝方法
 *
 * @author: TaoTaojs
 * @Date: 2020/7/3 2:36 PM
 * @Description:
 * <h1>对象复制工具</h1>
 * <h2>简介</h2>
 * 对对象及列表进行复制操作，常用于不同层的Bean对象相互复制
 * <h3>主要功能：</h3>
 * <ol>
 *     <li>两个不同类型对象的相同属性拷贝，支持黑白名单与指定属性</li>
 *     <li>两个不同类型集合的相同属性拷贝，支持黑白名单与指定属性</li>
 *     <li>对象深克隆</li>
 * </ol>
 * @关联类:
 * @参考链接：
 * @History: <br/>
 * <author>        <time>                      <version>          <desc>
 * TaoTaojs        2020/7/3 2:36 PM                V1.0
 */
public class EntityCopyUtil {

    EntityCopyUtil(){}

    /**
     * 将第一个对象的值复制到第二个对象中
     * @param sourceObj
     * @param targetObj
     * @param fields 需要复制的属性（可以为空，为空则默认复制全部，且受黑名单与白名单的约束）
     * @param notFields 黑名单
     * @param yesFields 白名单
     * @param <T1>
     * @param <T2>
     * @return
     */
    public static<T1, T2> void copyData(T1 sourceObj, T2 targetObj, Field[] fields, Field[] notFields, Field[] yesFields){
        if(sourceObj == null || targetObj == null){
            throw new MyInnerException("对象不能为null");
        }
        if(fields != null && fields.length > 0){
            //fields存在时,not和yes不生效，仅复制fields中的属性
            for (Field field : fields) {
                copyFieldValue(sourceObj, targetObj, field);
            }
        } else {
            //fields不存在则not和yes生效，循环所有字段并复制
            fields = sourceObj.getClass().getDeclaredFields();
            for(Field field : fields) {
                boolean notFlag = CollectionUtil.isExist(notFields, field, Field.class, "getName");
                boolean yesFlag = CollectionUtil.isExist(yesFields, field, Field.class, "getName");
                if(yesFlag || !notFlag){
                    copyFieldValue(sourceObj, targetObj, field);
                }
            }
        }
    }
    public static<T1,T2> T2 copyData(T1 sourceObj, Class<T2> targetClass, Field[] fields, Field[] notFields, Field[] yesFields){
        T2 targetObj = EntityUtil.getInstance(targetClass);
        copyData(sourceObj, targetObj, fields, notFields, yesFields);
        return targetObj;
    }

    /**
     * 将列表对象转换为另一个列表对象
     * @param sourceList
     * @param sourceClass
     * @param fields 需要复制的属性（可以为空，为空则默认复制全部，且受黑名单与白名单的约束）
     * @param notFields 黑名单
     * @param yesFields 白名单
     * @param <T1>
     * @param <T2>
     * @return
     */
    public static<T1,T2> List<T2> copyListData(Collection<T1> sourceList, Class<T2> sourceClass, Field[] fields, Field[] notFields, Field[] yesFields){
        List<T2> list = new ArrayList<>();
        for (T1 source : sourceList) {
            list.add(copyData(source, sourceClass, fields, notFields, yesFields));
        }
        return list;
    }

    /**
     * 将第一个对象的指定属性复制到第二个对象中
     * @param sourceObj
     * @param targetObj
     * @param field
     * @param <T1>
     * @param <T2>
     */
    private static <T1, T2> void copyFieldValue(T1 sourceObj, T2 targetObj, Field field) {
        T1 t1 = ReflectUtil.invokeGet(sourceObj, field);
        if(t1 != null) {
            ReflectUtil.invokeSet(targetObj, field, t1);
        }
    }


    /**
     * 复制字段的值？？？？这特么是啥
     * @param sourceObj 被转换的
     * @param targetObj 转换得到的 - null
     * @param field1
     * @param field2
     * @param <T1>
     * @param <T2>
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    public static <T1, T2> void copyFieldValue(T1 sourceObj, T2 targetObj, Field field1, Field field2) {
        //1. 判断两个字段是否名称相同而且类型相同
        if (field1.getName().equals(field2.getName())
                && EntityUtil.equalFieldsType(field1, field2)) {
            //2. 获取源数据字段的值
            field1.setAccessible(true);
            Object value = ReflectUtil.invokeGet(sourceObj, field1);
            //3. 给目标数据字段赋值
            field2.setAccessible(true);
            ReflectUtil.invokeSet(targetObj, field2, value);
            //4. 访问权限还原
            ReflectUtil.fieldAccess(false, field1, field2);
            return;
        }
        if (field1.getName().equals(field2.getName())){
            String fTypeName1 = field1.getType().getSimpleName();
            String fTypeName2 = field2.getType().getSimpleName();
            String dateName = Date.class.getSimpleName();
            String stringName = String.class.getSimpleName();
            ReflectUtil.fieldAccess(true, field1, field2);
            Object value = ReflectUtil.invokeGet(sourceObj, field1);
            if(value == null) {
                return;
            }
            if(fTypeName1.equals(stringName) && fTypeName2.equals(dateName)){

                field2.setAccessible(true);
                try {
                    ReflectUtil.invokeSet(targetObj, field2, new SimpleDateFormat("yyyy-MM-dd").parse((String)value));
                } catch (ParseException e) {
                    throw new MyInnerException(e);
                }

                //4. 访问权限还原
                ReflectUtil.fieldAccess(false, field1, field2);
            }
            if(fTypeName1.equals(dateName) && fTypeName2.equals(stringName)){
                ReflectUtil.fieldAccess(true, field1, field2);
                //3. 获取源数据字段的值给目标数据字段赋值
                ReflectUtil.invokeSet(targetObj, field2, new SimpleDateFormat("yyyy-MM-dd").format((Date)value));

                //4. 访问权限还原
                ReflectUtil.fieldAccess(false, field1, field2);
            }
        }
    }

    /**
     * 深克隆
     * @param orig
     * @return
     * @throws NotSerializableException
     */
    public static Object clone(Object orig) {
        Object obj = null;
        try {
            // Write the object out to a byte array
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(orig);
            out.flush();
            out.close();

            // Make an input stream from the byte array and read
            // a copy of the object back in.
            ObjectInputStream in = new ObjectInputStream(
                    new ByteArrayInputStream(bos.toByteArray()));
            obj = in.readObject();
        } catch (Exception e) {
            throw new MyInnerException(e);
        }
        return obj;
    }

}
